// This kernel perform vectorized floating-point addition/multiplication
// to demonstrate how parallel processing can accelerate computation
// 2020.06.17 by wangdong@bjtu.edu.cn
//#include "ap_int.h"
// #include <stdio.h> 


//加速的方式是使用rewind pipeline pragma，同时将在行存中移动数据与发送数据到下游结合在一起，方便做成pipeline，使得发送过程更均匀，更流畅，减少stream的流量波动
//有问题，为什么行存使用两倍的空间来回倒换--为了避免pipeline中前后步骤对同一个变量的访问（有的读有的写）造成的数据依赖
//为了使得数据发送更均匀？？同时方便做成流水，如果使用原始代码做成流水资源消耗太高。
#include "custypedef.h"


//--------------------- Baseline -----------------------//
//#define BUFFER_DEPTH 4096
extern "C" {
void featureRead(
            	DATA_TYPE *A_in,
				uint data_num,
				REFERENCE_STREAM(k2k<vec_type>, 256, img_channels)
				)
{
	#pragma HLS INTERFACE m_axi port = A_in  offset = slave bundle = gmem0 //num_read_outstanding=1 max_read_burst_length=1
	#pragma HLS INTERFACE axis  port = img_channels depth=256

	DATA_TYPE img_ori_0[BUFFER_DEPTH * 2];
	DATA_TYPE img_ori_1[BUFFER_DEPTH * 2];
	DATA_TYPE img_ori_2[BUFFER_DEPTH * 2];//注意这里，使用了两倍的buffer空间
	bool flag = 0;
	
	volatile int Rdcnt = 0;
	for(uint k = 0; k<DATA_SIZE_M/KERNAL_PARALLEL; k++){
		
		for(int i = -(VEC_SIZE) + 1; i < DATA_SIZE_H - DATA_SIZE_K + 1; i++)
		{

			for(int nn = 0; nn < DATA_SIZE_N; nn++){

				for(uint j = 0; j< DATA_SIZE_W; j++){
				#pragma HLS PIPELINE II=1 rewind
                    //#pragma HLS PIPELINE II=1 rewind  //如果这个循环体进行pipeline，当最内层循环结束时完全结束时才能开始下一次的最能层循环（就是说倒数第二内层的循环迭代一次）
					//使用了rewind，可以允许最内层所有循环执行完毕前开启倒数第二内存的循环，就是相当于把尽可能在执行的时候把多个层的循环当成同一个循环（展平）做成流水
					img_ori_2[j + nn * DATA_SIZE_W + flag * BUFFER_DEPTH] = A_in[j + (i + 2) * DATA_SIZE_W + nn * DATA_SIZE_W * DATA_SIZE_H];
				}
			}
			// if(i >= 0){
			for(uint s = 0; s<DATA_SIZE_W - DATA_SIZE_K + 1; s++){

				for(int nn = 0; nn < DATA_SIZE_N; nn++){ 

					for(uint s2 = 0; s2 < DATA_SIZE_K; s2++){
					#pragma HLS PIPELINE II=1 rewind
						k2k<vec_type> _trans_img_oris;
						DATA_TYPE tmp0, tmp1, tmp2;
						tmp0 = img_ori_0[s + s2 + nn * DATA_SIZE_W + flag * BUFFER_DEPTH];//从中拿出一个数
						tmp1 = img_ori_1[s + s2 + nn * DATA_SIZE_W + flag * BUFFER_DEPTH];//配合flag每次搬运的时候向做或向右的空间搬运，使得数据在两倍的区域里不停跳转，实际上似乎只用一倍区域就可以。这是为了pipeline
						                                                                  //当pipeline中出现数据依赖的时候，比如这里，如果使用单倍的buffer空间，在编译器将其制作成流水的时候会产生数据依赖
																						  //因为在一条流水中，上一环节和下一环节处理的是同一个变量，会干扰流水，甚至会产生错误的结果，正常情况下流水的不同环节不能相互依赖。因此在遇到此问题的时候，编译器会给出报错
																						  //为了处理此问题，制作了类似pingpang buffer的结构，使得读和写分开，拆解这个数据依赖。
						tmp2 = img_ori_2[s + s2 + nn * DATA_SIZE_W + flag * BUFFER_DEPTH];
						                                                                
						img_ori_0[s + s2 + nn * DATA_SIZE_W + (!flag) * BUFFER_DEPTH] = tmp1;//向下移动，但是注意，每次都只搬运一个数
						img_ori_1[s + s2 + nn * DATA_SIZE_W + (!flag) * BUFFER_DEPTH] = tmp2;

						if(i>=0){//移动的操作每次都进行，只是有时候是虚假的移动，没有实际意义，只有当真正读取到三片数据后再开始打包并传输数据，注意这里的发送是和数据向上移动同步的，数据取出来，一部分发送了，同时一部分移动了
							_trans_img_oris.data(GET_BIT(DATA_TYPE)-1, 0) = tmp0;
							_trans_img_oris.data(GET_BIT(DATA_TYPE)*2-1, GET_BIT(DATA_TYPE)) = tmp1;
							_trans_img_oris.data(GET_BIT(DATA_TYPE)*3-1, GET_BIT(DATA_TYPE)*2) = tmp2;
							img_channels.write(_trans_img_oris);
						}

						
						++Rdcnt;
					}
				}
			}
			flag = !flag;
			// }
			// for (int ll = 0;ll < DATA_SIZE_W * DATA_SIZE_N;ll++)
			// {
			// 	img_ori_0[ll] = img_ori_1[ll];
			// }
			// for (int ll = 0; ll < DATA_SIZE_W * DATA_SIZE_N; ll++)
			// {
			// 	img_ori_1[ll] = img_ori_2[ll];
			// }
		}
	}
}

}
